红外遥控学习,万能遥控解决方案 您所在的位置:网站首页 mate30 pro 红外遥控 红外遥控学习,万能遥控解决方案

红外遥控学习,万能遥控解决方案

2023-05-17 12:41| 来源: 网络整理| 查看: 265

红外遥控学习,万能遥控解决方案 1. 原理2. 思路3. 红外遥控接收3.1 初始化定时器3.2 定时器输入捕获3.3 获取数据3.4 红外接收测试3.5 测试过程 4. 发送程序4.1 初始化定时器和定时器的通道4.1发送函数

1. 原理

目前电视机、空调等家电大部分还是采用的红外遥控的,有时项目需要把遥控嵌入到自己的设备中,或者又是物联网需要控制家电,此时就需要智能学习和发送了,红外遥控电路图如下:

在这里插入图片描述 左侧为红外发送电路,右侧有红外接收电路。

● 发送端:

以NEC协议为例(实际测试中遵循NEC协议的不多),信息传输是基于38K载波,也就是说红外线是以载波的方式传递。

发送协议数据“0” = 发送载波560us + 不发送载波560us

发送协议数据“1” = 发送载波560us+ 不发送载波1680us

发送的波形如下图所示,下图中为 0 0 0 0 1 0 1 在这里插入图片描述 ● 接收端:

在红外接收端,如果接收到红外38K载波,则IR输出为低电平,如果不是载波包括固定低电平和固定高电平则输出高电平。在IR端接收的信号如下所示: 在这里插入图片描述 NEC协议规定:

发送协议数据“0” = 发送载波560us + 不发送载波560us

发送协议数据“1” = 发送载波560us+ 不发送载波1680us

发送引导码 = 发送载波9000us + 不发送载波4500us

● 总结:

1、接收端收到38K载波脉冲为低电平,没有则为高电平;所以在设计发送的时候,发送低电平应该开启38KHZ的PWM,发送高电平则关闭PWM(默认为高电平)

2、数据“0” 和数据“1” 是由接收到的一个高电平和一个低电平组合而来,一般来说高电平时间等于低电平时间为数据0,其它则为1

参考: STM32之红外遥控信号自学习实现

2. 思路

思路简单,简述为: 捕获——>保存——>发射

先捕获一个红外遥控按键的全部信息,不管内容是什么,再保存到flash中,就是多个高低电平的持续时间,然后再一一发射出去。这样不管是什么协议都能成功。

3. 红外遥控接收 3.1 初始化定时器

具体见代码和详细的注释:

// ir_inputCapture_send.c /******************************************************************* * @brief 定时器16输入捕获初始化/红外遥控初始化、设置IO以及TIM16_CH1的输入捕获、10ms溢出 ********************************************************************/ void TIM16_Remote_Init(void) { TIM_IC_InitTypeDef TIM16_CH1Config; TIM16_Handler.Instance = TIM16; //通用定时器16 TIM16_Handler.Init.Prescaler=(48-1); //预分频器,1M的计数频率,1us加1. TIM16_Handler.Init.CounterMode=TIM_COUNTERMODE_UP; //向上计数器 TIM16_Handler.Init.Period = (60000-1); //自动装载值,16位最大65536,此处设置60ms的计时 TIM16_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1; //时钟分频因子 HAL_TIM_IC_Init(&TIM16_Handler); //初始化TIM1输入捕获参数 TIM16_CH1Config.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING; //注意下降沿捕获 TIM16_CH1Config.ICSelection=TIM_ICSELECTION_DIRECTTI; //映射到TI4上 TIM16_CH1Config.ICPrescaler=TIM_ICPSC_DIV1; //配置输入分频,不分频 TIM16_CH1Config.ICFilter=0x03; // 0 //IC4F=0003 8个定时器时钟周期滤波 HAL_TIM_IC_ConfigChannel(&TIM16_Handler, &TIM16_CH1Config, TIM_CHANNEL_1);//配置TIM4通道4 __HAL_TIM_ENABLE_IT(&TIM16_Handler,TIM_IT_UPDATE); //使能更新中断 HAL_TIM_IC_Stop_IT(&TIM16_Handler,TIM_CHANNEL_1); } // stm32f0xx_hal_msp.c.c //定时器16底层驱动,时钟使能,引脚配置 //此函数会被上述的HAL_TIM_IC_Init()调用 //htim:定时器句柄 void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim) { GPIO_InitTypeDef GPIO_InitStruct; if(htim->Instance==TIM16) { /************TIM16_CH1************/ GPIO_InitTypeDef GPIO_Initure; __HAL_RCC_TIM16_CLK_ENABLE(); //使能TIM16时钟 __HAL_RCC_GPIOA_CLK_ENABLE(); //开启GPIOA时钟 GPIO_Initure.Pin = GPIO_PIN_6; //PB9 GPIO_Initure.Mode = GPIO_MODE_AF_PP; //复用输入 GPIO_Initure.Pull = GPIO_PULLUP; //上拉 GPIO_Initure.Speed = GPIO_SPEED_FREQ_HIGH; //高速 GPIO_Initure.Alternate = GPIO_AF5_TIM16; HAL_GPIO_Init(GPIOA,&GPIO_Initure); HAL_NVIC_SetPriority(TIM16_IRQn,2,0); //设置中断优先级,抢占优先级2,子优先级0 HAL_NVIC_EnableIRQ(TIM16_IRQn); //开启ITM16中断 } } 3.2 定时器输入捕获

红外编码是由多个字节编码组成的,根据持续的高低电平时间不同,而形成的编码信号,此时可以通过定时器捕获来实现。

定时器输入捕获中断回调函数如下,红外信号基本都是低电平起,可先用下降沿捕获:

// ir_inputCapture_send.c void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)//捕获中断发生时执行 { if(htim->Instance==TIM16) { if(READ_IR_GPIO()) //上升沿捕获 { Dval = HAL_TIM_ReadCapturedValue(&TIM16_Handler, TIM_CHANNEL_1); //读取CCR1也可以清CC4IF标志位 TIM_RESET_CAPTUREPOLARITY(&TIM16_Handler, TIM_CHANNEL_1); //一定要先清除原来的设置!! TIM_SET_CAPTUREPOLARITY(&TIM16_Handler, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING); //CC1P=1 设置为下降沿捕获 if(0 == irRun.receive.cnt) { __HAL_TIM_SET_COUNTER(&TIM16_Handler,0); //清空定时器值 return ; } if(irRun.receive.cnt RcvRemoteFinish(); } __HAL_TIM_SET_COUNTER(&TIM16_Handler,0); //清空定时器值 } else //下降沿捕获 { Dval = HAL_TIM_ReadCapturedValue(&TIM16_Handler, TIM_CHANNEL_1); TIM_RESET_CAPTUREPOLARITY(&TIM16_Handler,TIM_CHANNEL_1); TIM_SET_CAPTUREPOLARITY(&TIM16_Handler,TIM_CHANNEL_1,TIM_ICPOLARITY_RISING); //配置TIM16通道1上升沿捕获 //当前这一次,不计数,下一次上升沿才计数 if(0 == irRun.receive.cnt) { __HAL_TIM_SET_COUNTER(&TIM16_Handler,0); //清空定时器值 return; } if(irRun.receive.cnt irRun.receive.dataComplete = 1; } } else { RcvRemoteFinish(); } __HAL_TIM_SET_COUNTER(&TIM16_Handler,0); //清空定时器 放最后 } } } // ir_inputCapture_send.c //60m定时器计时后溢出中断的回调函数: void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim->Instance==TIM16) //定时器更新(溢出)中断回调函数 10ms溢出 { if(irRun.receive.dataComplete) //至少已经收到了20个字节,才算接收到一帧信息 { RcvRemoteFinish(); } else { irRun.receive.cnt = 0; //重新来过,此帧数据无效 } } } // ir_inputCapture_send.c /* 当前红外遥控一帧结束。 触发条件:① 接收到的红外字节个数已经超过最大允许的长度 (HAL_TIM_IC_CaptureCallback) ② 如上述的定时器计时后溢出中断的回调函数 (HAL_TIM_PeriodElapsedCallback) */ static void RcvRemoteFinish(void) { //1. 先失能 避免重洗掉前面保存的一帧完整数据 TIM16_Remote_Disable(); //2. 重新设置下降沿捕获,以防上升沿捕获的 TIM_SET_CAPTUREPOLARITY(&TIM16_Handler, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING); //CC1P=1 设置为下降沿捕获 //3-1. 清零数据存在的标志 irRun.receive.dataComplete = 0; //先清零 //4-2. (多等待了60ms),不管如何都算完成了 irRun.receive.complete = 1; // irRun.receive.complete 为一帧接收的完整标志 irRun.receive.len = irRun.receive.cnt - 1; //一帧接收的最大长度,irRun.receive.cnt 多了1个 //清零接收长度 缓冲数据 irRun.receive.cnt = 0; }

至此为止 数据保存在 irRun.buf 中, 个数为 irRun.receive.len 。

3.3 获取数据

具体见代码和详细的注释:

// 获取一帧数据在上层调用 // 返回值: 0, 没有任何按键按下 // 1 ,按下的按键键值. // ir_inputCapture_send.c u8 GetRemoteRcvBuf(uint16_t *buf, uint32_t *len, uint8_t isRecord) { if(irRun.receive.complete && !irRun.receive.dataComplete) { //完成一帧的标记, 且现在没有正在捕获的信号 TIM_SET_CAPTUREPOLARITY(&TIM16_Handler, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING); //CC1P=1 设置为下降沿捕获 //最多拷贝MAX_IR_LEARN_DATA_SIZE个, 第二次保护,前有一次保护措施 if(irRun.receive.len >= MAX_IR_LEARN_DATA_SIZE-1) irRun.receive.len = MAX_IR_LEARN_DATA_SIZE-1; if(irRun.receive.len >= GetFuncLen() && GetFuncLen()>20 && isRecord) { irRun.receive.len = GetFuncLen(); if(irRun.receive.len >= MAX_IR_COMPARE) irRun.receive.len = MAX_IR_COMPARE; } for(uint8_t i=0; i u8 ack = 0; TIM16_Remote_Enable(); //使能红外遥控捕获 while(1) { ack = GetRemoteRcvBuf(&userSave.irFunctionBuf[0], &userSave.irFunctionLen, 0); if(ack == 1) { ack = 0; LED_ON(); test_cnt++; TIM16_Remote_Enable(); //使能红外遥控捕获 } LED_OFF(); } } #endif 3.5 测试过程

左边使用 Keil 的 Debug调试,捕获一帧红外遥控信号;

右边使用 Kingst 数字信号逻辑分析仪,接收到的此帧数据;

遥控器采用三星的电视机遥控器(不是典型NEC协议),对比如下,左边任意一个字节的数字(持续时间us)都能与右边完美对应上,证明了以上程序的可行性。 在这里插入图片描述

4. 发送程序

原理弄懂,红外发送程序相对来说就简单很多

4.1 初始化定时器和定时器的通道

定时器不分频的情况下,需要产生38KHZ的频率的方波,Period 需要设置成: 48000/38=1263.158

具体见代码和详细的注释:

//ir_inputCapture_send.c //定时器不分频,需要产生38KHZ的频率的方波,Period 需要设置成: 48000/38=1263.158 void TIM3_PWM_Init(void) { TIM_MasterConfigTypeDef MasterConfig; TIM3_Handler.Instance = TIM3; //定时器3 TIM3_Handler.Init.Prescaler = 0; //定时器分频 TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP; //向上计数模式 TIM3_Handler.Init.Period = 1264; ///48000/38=1263.158 TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1; TIM3_Handler.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; HAL_TIM_PWM_Init(&TIM3_Handler); //初始化PWM MasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; MasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&TIM3_Handler, &MasterConfig); TIM3_CH4Handler.OCMode = TIM_OCMODE_PWM1; //模式选择PWM1 TIM3_CH4Handler.Pulse = 632; //设置比较值,此值用来确定占空比,默认比较值为自动重装载值的一半,即占空比为50% TIM3_CH4Handler.OCPolarity = TIM_OCPOLARITY_HIGH; //输出比较极性为低 TIM3_CH4Handler.OCFastMode = TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH4Handler,TIM_CHANNEL_4); //配置TIM3通道4 HAL_TIM_PWM_MspInit(&TIM3_Handler); HAL_TIM_PWM_Stop(&TIM3_Handler,TIM_CHANNEL_4);//关闭PWM通道4 } 4.1发送函数

发送函数记住核心即可:低电平的时候开启PWM,高电平关闭。

/******************************************************************* * @brief 红外PWM控制的发送,在轮询中操作 低电平的时候开启PWM,高电平关闭 * @retval None ********************************************************************/ void IrPwmMultiSend(u16 *buf, u32 len) { //1、关闭红外接收中断 TIM16_Remote_Disable(); //停止捕获TIM16的通道1 //先停止 HAL_TIM_PWM_Stop(&TIM3_Handler,TIM_CHANNEL_4); delay_ms(10); for(u8 i=0; i HAL_TIM_PWM_Start(&TIM3_Handler,TIM_CHANNEL_4); } else { HAL_TIM_PWM_Stop(&TIM3_Handler,TIM_CHANNEL_4); } delay_us(buf[i]); } HAL_TIM_PWM_Stop(&TIM3_Handler,TIM_CHANNEL_4); TIM16_Remote_Enable(); //开始捕获TIM16的通道1 }

上层调用:

//app.c IrPwmMultiSend(&userSave.irPowerBuf[0], userSave.irPowerLen); //发送开机指令

验证的话,直接看要操控的目标,如电视机是否生效了,如果不行,在验证发出的波形,是否满足要求。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有